package hku.cs.lunuo;

import hku.cs.lunuo.tools.DirTools;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import junit.runner.BaseTestRunner;
import junit.runner.StandardTestSuiteLoader;
import junit.runner.TestSuiteLoader;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;

public class MyRunner3 extends BaseTestRunner {
	PrintStream fWriter;
	public static final int SUCCESS_EXIT= 0;
	public static final int FAILURE_EXIT= 1;
	public static final int EXCEPTION_EXIT= 2;
	
	
	private File conPath = null;
	boolean currentTestResult = true;
	private String resultDirPath;
	private HashMap<String, HashMap<String, Boolean>> result = new HashMap<String, HashMap<String, Boolean>>();
	List<Element> tstcases = new ArrayList<Element>();
	private Set<String> tmpSet = new HashSet<String>();
	
//	private int allTestsCnt=0;
	private int currentTestsIdx=0;
	
	public MyRunner3() {
		fWriter = System.out;
	}

	public MyRunner3(String resultDirPath) {
		this();
		this.resultDirPath = resultDirPath;
		DirTools.newTest(resultDirPath);// fix bug, at first it is put at
										// sesstionstart, but then
		// junit create result files, so clear the directory can't be complete.
	}

	public void testFailed(int status, Test test, Throwable t) {
		currentTestResult = false;
	}

	public void testStarted(String testName) {
		currentTestResult = true;
	}

	public TestSuiteLoader getLoader() {
		return new StandardTestSuiteLoader();
	}

	public void testEnded(String testName) {
		currentTestsIdx++;
		System.out.println("finish test "+currentTestsIdx+", Name:"+testName+", status:"+currentTestResult);//+" ("+allTestsCnt+")");
		String testCaseName = testName.substring(testName.indexOf('(') + 1,
				testName.indexOf(')'));
		testName = testName.substring(0, testName.indexOf('('));

		HashMap<String, Boolean> inMap = result.get(testCaseName);

		if (inMap == null)
			inMap = new HashMap<String, Boolean>();

		int max = 0;
		boolean is = false;
		for (String s : inMap.keySet()) {
			if (s.startsWith(testName)) {
				String[] xx = s.split("\\.");
				if (xx.length == 2) {
					if (Integer.parseInt(xx[1]) > max)
						max = Integer.parseInt(xx[1]);
				}
				is = true;
			}
		}
		if (is) {
			max++;
			testName += "." + max;
		}
		inMap.put(testName, currentTestResult);
		result.put(testCaseName, inMap);
	}

	protected void runFailed(String message) {
//		System.err.println(message);
//		System.exit(FAILURE_EXIT);
	}

	protected TestResult createTestResult() {
		return new TestResult();
	}

	private boolean isSame(String a, String b) {
		if (a.startsWith(b)) {
			if (a.length() > b.length()) {
				if (a.charAt(b.length()) == '.')
					return true;
				else
					return false;
			} else
				return true;
		}
		return false;
	}

	public boolean constructCate(Test test, Element xmlE) {
		if (test instanceof TestCase) {
			Element e = new Element("testmethod");
			String name = ((TestCase) test).getName();
			int max = 0;
			boolean is = false;
			for (String s : tmpSet) {
				if (isSame(s, name)) {
					String[] xx = s.split("\\.");
					if (xx.length == 2) {
						if (Integer.parseInt(xx[1]) > max)
							max = Integer.parseInt(xx[1]);
					}
					is = true;
				}
			}
			if (is) {
				max++;
				name += "." + max;
			}
			e.setAttribute("name", name);
			tmpSet.add(name);
			xmlE.addContent(e);
			return false;
		} else if (test instanceof TestSuite) {
			TestSuite t = (TestSuite) test;
			Element e = new Element("testsuite");
			String naxx = t.getName();
			if (naxx == null || "".equals(naxx))
				naxx = "Anonymous_Testsuite";

			e.setAttribute("name", naxx);
			xmlE.addContent(e);
			boolean isTestCase = true;
			for (int i = 0; i < t.testCount(); i++) {
				if (constructCate(t.testAt(i), e)) {
					isTestCase = false;
				}
			}
			if (isTestCase) {
				e.setName("testcase");

				tstcases.add(e);

				tmpSet.clear();
			}
			return true;
		}
		return true;
	}

	public TestResult doRun(Test suite, boolean isOneTestCase) {
		TestResult testresult = createTestResult();
		testresult.addListener(this);
		

//		allTestsCnt = testresult.runCount();
//		System.out.println(allTestsCnt);
		
		suite.run(testresult);

		printFooter(testresult);
		// Enumeration exx = result.failures();
		// while(exx.hasMoreElements()) {
		// TestFailure f = (TestFailure)exx.nextElement();
		//			
		// }

		System.out.println("start write result report");
		runEnd();
		System.out.println("end write result report");
		// TODO should set correct soot path

//		for (String rkey : result.keySet()) {
//			HashMap<String, Boolean> inResult = result.get(rkey);
//			String tmp = resultDirPath + rkey + File.separator;
//			if (inResult.containsValue(false)) {
//				for (String key : inResult.keySet()) {
//					String[] args = new String[] { tmp + key + File.separator };
//					Check.main(args);
//				}
//			}
//		}

		System.out.println("start write construct report");
		int a = resultDirPath.lastIndexOf('/');
		int b = resultDirPath.lastIndexOf('/', resultDirPath.length() - 2);
		String c = resultDirPath.substring(b + 1, a);

		Element xmlRoot = new Element("Test_Session");
		xmlRoot.setAttribute("name", c);
		Document xmlDoc = new Document(xmlRoot);

		if (isOneTestCase) {
			Element xmlSuite = new Element("testsuite");
			xmlSuite.setAttribute("name", "Anonymous_Testsuite");
			xmlRoot.addContent(xmlSuite);
			constructCate(suite, xmlSuite);
		} else
			constructCate(suite, xmlRoot);

		List<String> tmpLst = new LinkedList<String>();

		for (Element el : tstcases) {
			String name = el.getAttributeValue("name");
			if ("Anonymous_Testsuite".equals(name)) {
				List xmlMethods = el.getChildren();
				for (int i = 0; i < xmlMethods.size(); i++) {
					Element e = (Element) xmlMethods.get(i);
					String s = e.getAttributeValue("name");
					tmpLst.add(s);
				}
				name = getTestCaseName(tmpLst, result);

				el.setAttribute("name", name);
			}
			boolean isSuc = true;
			if (result.get(name).containsValue(false))
				isSuc = false;
//			System.out.println("name: " + name + "  " + "status: " + isSuc);
			el.setAttribute("status", String.valueOf(isSuc));
			while (el.getParentElement() != null) {
				el = el.getParentElement();
				String status = el.getAttributeValue("status");
				if(status==null || status=="true")
					el.setAttribute("status", String.valueOf(isSuc));
			}
			tmpLst.clear();
		}

		try {
			conPath = new File(resultDirPath, "construct.xml");
			new XMLOutputter().output(xmlDoc, new FileOutputStream(conPath));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		System.out.println("end write construct report");

		return testresult;
	}

	private String getTestCaseName(List<String> tmpLst,
			HashMap<String, HashMap<String, Boolean>> r) {
		for (String rkey : r.keySet()) {
			boolean tr = true;
			for (String s : tmpLst) {
				if (!r.get(rkey).containsKey(s)) {
					tr = false;
					break;
				}
			}
			if (tr)
				return rkey;
		}
		return null;
	}

	public TestResult startOneTest(String args[]) throws Exception {
		String testCase = args[0];
		Test suite = getTest(testCase);
		return doRun(suite, true);
	}

	public TestResult startTests(String args[]) throws Exception {
		String testCase = args[0];
		Test suite = getTest(testCase);
		return doRun(suite, false);
	}

	public static void main(String[] args) {
		
		
		MyRunner3 runner = new MyRunner3(args[0]);
		try {
			TestResult r;
			Class suite = Class.forName(args[1]);
			if (suite.getSuperclass().equals(TestCase.class))
				 r =runner.startOneTest(new String[] { args[1] });
			else
				r = runner.startTests(new String[] { args[1] });
				
			if (!r.wasSuccessful()) 
				System.exit(FAILURE_EXIT);
			System.exit(SUCCESS_EXIT);
		} catch(Exception e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
			System.exit(EXCEPTION_EXIT);			
		}
	}
	
	public void runEnd() {
		for (String rkey : result.keySet()) {
			HashMap<String, Boolean> inResult = result.get(rkey);

			Element xmlRoot = new Element("testcase");
			xmlRoot.setAttribute("name", rkey);
			xmlRoot.setAttribute("number", Integer.toString(inResult.size()));
			if (inResult.values().contains(false))
				xmlRoot.setAttribute("status", "false");
			else
				xmlRoot.setAttribute("status", "true");
			Document xmlDoc = new Document(xmlRoot);

			for (String key : inResult.keySet()) {
				Element xmlTestMethod = new Element("testmethod");
				xmlTestMethod.setAttribute("name", key);
				xmlTestMethod.setAttribute("status", ((Boolean) inResult
						.get(key)).toString());
				xmlRoot.addContent(xmlTestMethod);
			}

			try {
				File f = DirTools.prepare(new File(resultDirPath, rkey));
				File fz = new File(f, "result.xml");
				new XMLOutputter().output(xmlDoc, new FileOutputStream(fz));
				System.out.println("write result file to " + fz.toString());
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void printFooter(TestResult result) {
		if (result.wasSuccessful()) {
			getWriter().println();
			getWriter().print("OK");
			getWriter().println (" (" + result.runCount() + " test" + (result.runCount() == 1 ? "": "s") + ")");

		} else {
			getWriter().println();
			getWriter().println("FAILURES!!!");
			getWriter().println("Tests run: "+result.runCount()+ 
				         ",  Failures: "+result.failureCount()+
				         ",  Errors: "+result.errorCount());
			
		}
	    getWriter().println();
	}
	
	public PrintStream getWriter() {
		return fWriter;
	}
	

}
